<?php

namespace Plugins\AdminBasic\Http\Admin\Service\MakeCURDFiles;

use App\Exception\ApiMessageException;
use App\Http\AbstractController;
use App\Middlewares\Admin\AuthenticateMiddleware;
use App\Middlewares\Admin\PermissionsMiddleware;
use App\Util\Response;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use Hyperf\HttpServer\Annotation\Middlewares;
use Hyperf\HttpServer\Annotation\PostMapping;
use Hyperf\Stringable\Str;
use Hyperf\Validation\Annotation\Scene;
use Hyperf\Validation\Middleware\ValidationMiddleware;
use Hyperf\View\Render;
use JetBrains\PhpStorm\ExpectedValues;
use Psr\Http\Message\ResponseInterface;
use Psr\SimpleCache\InvalidArgumentException;
use Sc\Util\ClassFile\Components\Attribute;
use Sc\Util\ClassFile\Components\ClassFileConstruction;
use Sc\Util\ClassFile\Components\FunctionParam;
use Sc\Util\ClassFile\Components\Method;
use Sc\Util\ClassFile\Components\Out\RawOut;

/**
 * Class MakeController
 */
class MakeController extends AbstractMake
{

    public function make(): string
    {
        $controller = new ClassFileConstruction(Str::studly($this->table) . 'Controller');
        $controller->setNamespace($this->getNamespace('Controller'));
        $controller->setDocBlock($this->tableComment);
        $this->addClassUses($controller);
        $controller->setExtends(AbstractController::class);

        $this->addAttributes($controller);
        $this->addMethods($controller);

        return $this->makeFile($this->getFilename('Controller'), $controller->out());
    }

    private function addClassUses(ClassFileConstruction $controller): void
    {
        $controller->addUses(AbstractController::class);
        $controller->addUses(AuthenticateMiddleware::class);
        $controller->addUses(PermissionsMiddleware::class);
        $controller->addUses(Response::class);
        $controller->addUses(Controller::class);
        $controller->addUses(GetMapping::class);
        $controller->addUses(Middlewares::class);
        $controller->addUses(Render::class);
        $controller->addUses(ResponseInterface::class);
        $controller->addUses($this->getServiceClass($controller));

        if (array_intersect($this->controllerMethod, ['store', 'update', 'delete'])) {
            $controller->addUses(PostMapping::class);
            $controller->addUses(Scene::class);
            $controller->addUses(ApiMessageException::class);
            $controller->addUses(ValidationMiddleware::class);
            $controller->addUses($this->getValidateClass());
            $controller->addUses(InvalidArgumentException::class);
        }
    }

    private function getServiceClass($controller): string
    {
        return strtr($controller->getGlobalClassname(), ['Controller' => 'Service']);
    }

    private function getValidateClass(): string
    {
        $requestName = $this->getAddPrefixStr(Str::studly($this->dir), '\\') . '\\' . Str::studly($this->table) . 'Request';
        if ($this->isPlugins) {
            return "Plugins\\{$this->plugins}\\Requests" . $requestName;
        }

        return "App\\Requests" . $requestName;
    }

    private function addAttributes(ClassFileConstruction $controller): void
    {
        $controller->addAttributes(function () use ($controller){
            $attribute = new Attribute($controller->getAppropriateClassName(Controller::class));
            $attribute->addParam("admin"
                . $this->getAddPrefixStr(Str::kebab($this->plugins), '/plugins/')
                . $this->getAddPrefixStr(Str::kebab($this->dir), '/')
                . '/' . Str::kebab($this->table), 'prefix');

            return $attribute;
        });

        $controller->addAttributes(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(Middlewares::class));
            $attribute->addParam([
                new RawOut($controller->getAppropriateClassName(AuthenticateMiddleware::class)),
                new RawOut($controller->getAppropriateClassName(PermissionsMiddleware::class)),
            ]);

            return $attribute;
        });
    }

    private function addMethods(ClassFileConstruction $controller): void
    {
        $controllerMethod = $this->controllerMethod;
        if (array_filter($this->fields, fn($field) => ($field['lists_type'] ?? null) === 'switch')) {
            $controllerMethod[] = 'switch';
        }

        foreach ($controllerMethod as $method) {
            match ($method) {
                'lists'     => $this->listsMethod($controller),
                'listsData' => $this->listsDataMethod($controller),
                'create'    => $this->createMethod($controller),
                'store'     => $this->storeMethod($controller),
                'edit'      => $this->editMethod($controller),
                'update'    => $this->updateMethod($controller),
                'delete'    => $this->destroyMethod($controller),
                'switch'    => $this->switchToggleMethod($controller),
            };
        }
    }

    private function listsMethod(ClassFileConstruction $controller): void
    {
        $method = new Method('lists');
        $method->setDocBlockComment([
            '列表页', '',
            '@param Render $render', '',
            '@return ResponseInterface',
        ]);
        $method->setReturnType($controller->getAppropriateClassName(ResponseInterface::class));

        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(GetMapping::class));
            $attribute->addParam('lists', 'path');
            return $attribute;
        });

        $method->addParameters(function () use ($controller){
            $param = new FunctionParam("render");
            $param->setType($controller->getAppropriateClassName(Render::class), $controller);
            return $param;
        });

        $method->addCode(
            "return \$render->render('{$this->getViewPath('lists')}');"
        );

        $controller->addMethods($method);
    }

    private function listsDataMethod(ClassFileConstruction $controller): void
    {
        $method  = new Method('listsData');
        $service = $controller->getAppropriateClassName($this->getServiceClass($controller));
        $method->setDocBlockComment([
            '列表数据', '',
            '@return ResponseInterface',
        ]);
        $method->setReturnType($controller->getAppropriateClassName(ResponseInterface::class));

        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(GetMapping::class));
            $attribute->addParam('lists-data', 'path');
            return $attribute;
        });

        $method->addCode(
            "return Response::json()->success({$service}::aop()->lists());"
        );

        $controller->addMethods($method);
    }


    private function createMethod(ClassFileConstruction $controller): void
    {
        $method  = new Method('create');
        $method->setDocBlockComment([
            '创建数据页面', '',
            "@param Render \$render", '',
            '@return ResponseInterface',
        ]);
        $method->setReturnType($controller->getAppropriateClassName(ResponseInterface::class));

        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(GetMapping::class));
            $attribute->addParam('create', 'path');
            return $attribute;
        });

        $method->addParameters(function () use ($controller){
            $param = new FunctionParam("render");
            $param->setType($controller->getAppropriateClassName(Render::class), $controller);
            return $param;
        });
        $method->addCode(
            "return \$render->render('{$this->getViewPath('create')}');"
        );
        $controller->addMethods($method);
    }

    private function storeMethod(ClassFileConstruction $controller): void
    {
        $method  = new Method('store');
        $request = $controller->getAppropriateClassName($this->getValidateClass());
        $service = $controller->getAppropriateClassName($this->getServiceClass($controller));
        $method->setDocBlockComment([
            '创建数据', '',
            "@param $request \$request", '',
            '@return ResponseInterface',
            '@throws ApiMessageException',
            '@throws InvalidArgumentException',
        ]);
        $method->setReturnType($controller->getAppropriateClassName(ResponseInterface::class));
        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(Scene::class));
            $attribute->addParam('store');
            return $attribute;
        });

        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(PostMapping::class));
            $attribute->addParam('create', 'path');
            return $attribute;
        });

        $this->generateUpdateParams($method, $controller, $request);
        $method->addCode("{$service}::aop()->store(\$request->post());", "return Response::json()->success();");

        $controller->addMethods($method);
    }

    private function editMethod(ClassFileConstruction $controller): void
    {
        $method  = new Method('edit');
        $method->setDocBlockComment([
            '更新数据页面', '',
            "@param Render \$render", '',
            '@return ResponseInterface',
        ]);
        $method->setReturnType($controller->getAppropriateClassName(ResponseInterface::class));

        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(GetMapping::class));
            $attribute->addParam('update', 'path');
            return $attribute;
        });
        $method->addParameters(function () use ($controller){
            $param = new FunctionParam("render");
            $param->setType($controller->getAppropriateClassName(Render::class), $controller);
            return $param;
        });
        $method->addCode(
            "return \$render->render('{$this->getViewPath('edit')}');"
        );
        $controller->addMethods($method);
    }

    private function updateMethod(ClassFileConstruction $controller): void
    {
        $method  = new Method('update');
        $request = $controller->getAppropriateClassName($this->getValidateClass());
        $service = $controller->getAppropriateClassName($this->getServiceClass($controller));
        $method->setDocBlockComment([
            '更新数据', '',
            "@param $request \$request", '',
            '@return ResponseInterface',
            '@throws ApiMessageException',
            '@throws InvalidArgumentException',
        ]);
        $method->setReturnType($controller->getAppropriateClassName(ResponseInterface::class));
        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(Scene::class));
            $attribute->addParam('update');
            return $attribute;
        });
        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(PostMapping::class));
            $attribute->addParam('update', 'path');
            return $attribute;
        });
        $this->generateUpdateParams($method, $controller, $request);
        $method->addCode("{$service}::aop()->update(\$request->post());", "return Response::json()->success();");

        $controller->addMethods($method);
    }

    private function destroyMethod(ClassFileConstruction $controller): void
    {
        $method  = new Method('destroy');
        $request = $controller->getAppropriateClassName($this->getValidateClass());
        $service = $controller->getAppropriateClassName($this->getServiceClass($controller));
        $method->setDocBlockComment([
            '删除数据', '',
            "@param $request \$request", '',
            '@return ResponseInterface',
            '@throws ApiMessageException',
        ]);
        $method->setReturnType($controller->getAppropriateClassName(ResponseInterface::class));
        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(Scene::class));
            $attribute->addParam('destroy');
            return $attribute;
        });
        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(PostMapping::class));
            $attribute->addParam('destroy', 'path');
            return $attribute;
        });

        $this->generateUpdateParams($method, $controller, $request);
        $method->addCode("{$service}::aop()->destroy(\$request->post('ids'));", "return Response::json()->success();");
        $controller->addMethods($method);
    }


    private function switchToggleMethod(ClassFileConstruction $controller): void
    {
        $method  = new Method('switchToggle');
        $request = $controller->getAppropriateClassName($this->getValidateClass());
        $service = $controller->getAppropriateClassName($this->getServiceClass($controller));
        $method->setDocBlockComment([
            '开关切换数值更新', '',
            "@param $request \$request",
            '@return ResponseInterface',
            '@throws ApiMessageException',
        ]);
        $method->setReturnType($controller->getAppropriateClassName(ResponseInterface::class));
        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(PostMapping::class));
            $attribute->addParam('switch-toggle', 'path');
            return $attribute;
        });
        $method->addParameters(function () use ($controller){
            $param = new FunctionParam("request");
            $param->setType($controller->getAppropriateClassName($this->getValidateClass()), $controller);
            return $param;
        });

        $method->addCode("{$service}::aop()->toggle(\$request->post());", "return Response::json()->success();");

        $controller->addMethods($method);
    }

    private function getViewPath(#[ExpectedValues(['lists', 'create', 'edit',])] string $type): string
    {
        $dir     = $this->getAddPrefixStr($this->dir, '.');
        $plugins = $this->isPlugins ? "plugins.$this->plugins." : '';
        $table   = Str::studly($this->table);

        return match ($type) {
            'lists'  => "{$plugins}Admin{$dir}.{$table}.lists",
            'create', 'edit' => "{$plugins}Admin{$dir}.{$table}.form",
            default  => "{$plugins}Admin{$dir}.{$type}",
        };
    }

    /**
     * @param Method                $method
     * @param ClassFileConstruction $controller
     * @param string                $request
     *
     * @return void
     */
    private function generateUpdateParams(Method $method, ClassFileConstruction $controller, string $request): void
    {
        $method->addAttribute(function () use ($controller) {
            $attribute = new Attribute($controller->getAppropriateClassName(Middlewares::class));
            $attribute->addParam([
                new RawOut($controller->getAppropriateClassName(ValidationMiddleware::class))
            ]);
            return $attribute;
        });

        $method->addParameters(function () use ($request, $controller) {
            $param = new FunctionParam("request");
            $param->setType($request, $controller);
            return $param;
        });
    }
}